package com.lucifer.d.security.gateway.service.filter;

import com.alibaba.fastjson.JSON;
import com.lucifer.d.security.annotation.model.Role;
import com.lucifer.d.security.annotation.model.RoleMap;
import com.lucifer.d.security.annotation.model.UserId;
import com.lucifer.d.security.exception.AuthException;
import com.lucifer.d.security.exception.SecurityReqException;
import com.lucifer.d.security.exception.TokenException;
import com.lucifer.d.security.service.gateway.content.SurroundContent;
import com.lucifer.d.security.service.gateway.model.AuthPacking;
import com.lucifer.d.security.service.gateway.properties.BootConfigProperties;
import com.lucifer.d.security.transfer.UserTransfer;
import com.lucifer.d.security.util.ReflectUtil;
import com.lucifer.d.util.RedissonUtil;
import com.lucifer.d.util.StringUtil;
import lombok.Data;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBufferFactory;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.lang.reflect.InvocationTargetException;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * @author Lucifer
 * @create 2023 - 03 - 05 15:48
 */
@Data
public class SecurityFilter implements GlobalFilter, Ordered {

    @Autowired
    private RedissonClient redissonClient;

    @Autowired
    private UserTransfer userTransfer;

    @Autowired
    private BootConfigProperties bootConfigProperties;

    private List<String> permits;

    public SecurityFilter(List<String> permits) {
        this.permits = permits;
    }

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        // 网关过滤模式时进行此拦截
        if (bootConfigProperties.isEnable()) {
            // 仅网关过滤中心触发拦截
            if (bootConfigProperties.isServer()) {
                // FIXME 判断是否正在进行security业务锁，如果存在业务锁，驳回业务请求
                RLock lock = RedissonUtil.getLock(redissonClient, SurroundContent.CONFIG_BOOT_LOCK);
                if (lock.isLocked()) {
                    throw new SecurityReqException();
                }
                Boolean acrossTicket = false;
                // 获取请求协议
                String method = request.getMethod().name();
                // 获取请求地址
                String url = request.getURI().getPath();
                // 放行拦截
                if (permitUrl(permits, url)) {
                    return errorResponse(exchange, chain);
                }
                // 头部‘/’及尾部‘？’参数过滤
                url = method + "/" + ("/".equals(String.valueOf(url.charAt(0))) ? url.substring(1) : url);
                List<String> urlSplit = Arrays.asList(url.split("/")).stream().collect(Collectors.toList());
                // 移除gateway转发路径前缀
                urlSplit.remove(1);
                // key移除方法前缀
                Iterator<String> keys = RedissonUtil.getKeys(redissonClient, SurroundContent.AUTH_MENU_KEY + ":" + method + "/" + urlSplit.get(1));
                // 请求目标key匹配
                String realKey = null;
                x:
                while (keys.hasNext()) {
                    // 排除权限图前缀+“：”
                    String key = keys.next().substring(SurroundContent.AUTH_MENU_KEY.length() + 1);
                    String[] keySplit = key.split("/");
                    if (keySplit.length != urlSplit.size()) {
                        continue;
                    }
                    for (int i = 0; i < keySplit.length; i++) {
                        // 占位检测，跳过判断
                        if (keySplit[i].matches(".*\\{.*\\}.*")) {
                            continue;
                        }
                        if (!keySplit[i].equals(urlSplit.get(i))) {
                            continue x;
                        }
                    }
                    realKey = key;
                    break;
                }
                if (!StringUtil.isEmpty(realKey)) {
                    // 权限图获取
                    Object authVerify = RedissonUtil.get(redissonClient, SurroundContent.AUTH_MENU_KEY + ":" + realKey);
                    // 权限图不存在该访问配置，则视为无需权限验证，直接放行
                    if (authVerify == null) {
                        acrossTicket = true;
                    } else {
                        // 获取用户信息
                        Object securityUser = userTransfer.getSecurityUser(request);
                        if (securityUser == null) {
                            throw new TokenException(TokenException.CODE302);
                        }
                        try {
                            if (userTransfer.getSecurityConfigModel().getPassRoleId().equals(ReflectUtil.getObjFieldOnReflect(securityUser, UserId.class))) {
                                acrossTicket = true;
                            } else {
                                // 用户验证角色信息载体
                                Object roleMessage = ReflectUtil.getObjFieldOnReflect(securityUser, Role.class);
                                if (roleMessage == null) {
                                    throw new AuthException("用户不存在对应角色/权限信息");
                                }
                                // 用户验证权限图
                                Object authMapObj = ReflectUtil.getObjFieldOnReflect(roleMessage, RoleMap.class);
                                if (authMapObj == null) {
                                    throw new AuthException("用户不存在对应角色/权限信息");
                                }
                                Map<String, Object> authMapOnAnalysis = (Map<String, Object>) authMapObj;
                                // 权限图内容解析（转换AuthPacking）
                                AuthPacking authPacking = JSON.parseObject((String) authVerify, AuthPacking.class);
                                // 范围验证
                                Object scopeVerify = authMapOnAnalysis.get(authPacking.getScope());
                                if (scopeVerify == null) {
                                    throw new AuthException("该用户不具备此权限");
                                }
                                // get权限在具备scope权限时默认放行
                                if (authPacking.getAuth().equals("get")) {
                                    acrossTicket = true;
                                } else {
                                    // 权限选项验证
                                    List<String> authMapVerify = (List<String>) scopeVerify;
                                    for (String auth : authMapVerify) {
                                        // 权限选项匹配则放行
                                        if (auth.equals(authPacking.getAuth())) {
                                            acrossTicket = true;
                                            break;
                                        }
                                    }
                                }
                            }
                        } catch (NoSuchMethodException e) {
                            throw new RuntimeException(e);
                        } catch (InvocationTargetException e) {
                            throw new RuntimeException(e);
                        } catch (IllegalAccessException e) {
                            throw new RuntimeException(e);
                        }
                    }
                }
                // 权限图不存在该访问配置，则视为无需权限验证，直接放行
                else {
                    acrossTicket = true;
                }
                if (!acrossTicket) {
                    throw new AuthException("该用户不具备此权限");
                }
            }
        }
        return errorResponse(exchange, chain);
    }

    private Boolean permitUrl(List<String> permits, String url) {
        Boolean isPermit = false;
        for (String permit : permits) {
            if (url.contains(permit)) {
                isPermit = true;
                break;
            }
        }
        return isPermit;
    }

    private Mono<Void> errorResponse(ServerWebExchange exchange, GatewayFilterChain chain) {
        return chain.filter(exchange).onErrorResume((ex) -> createExceptionResponse(exchange, ex));
    }

    private Mono<Void> createExceptionResponse(ServerWebExchange exchange, Throwable ex){
        HttpStatus status;
        if (ex instanceof AuthException) {
            status = HttpStatus.PERMANENT_REDIRECT;
        } else if (ex instanceof TokenException) {
            status = HttpStatus.MOVED_TEMPORARILY;
        } else {
            status = HttpStatus.INTERNAL_SERVER_ERROR;
            ex.printStackTrace();
        }
        // 构建错误响应体
        ServerHttpResponse response = exchange.getResponse();
        // 设置响应内容为JSON格式
        byte[] bytes = ("{" + "\"statusCode\": " + status.value() + "," + "\"message\": \"" + ex.getMessage() + "\"}").getBytes(StandardCharsets.UTF_8);
        DataBufferFactory bufferFactory = response.bufferFactory();
        // 设置响应头和状态码
        response.getHeaders().setContentType(MediaType.APPLICATION_JSON);
        response.setStatusCode(status);
        // 返回错误信息
        return response.writeWith(Mono.just(bufferFactory.wrap(bytes)));
    }

    @Override
    public int getOrder() {
        return 0;
    }

}
